using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;

namespace EaMarkdown
{
    /// <summary>
    /// 
    /// </summary>
    public static class MarkdownUtil
    {
        /// <summary>
        /// 
        /// </summary>
        /// <param name="markdown"></param>
        /// <returns></returns>
        public static string Md2Html(string markdown)
        {
            if (string.IsNullOrEmpty(markdown))
            {
                return markdown;
            }

            return new Converter(markdown).ToHtml();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="value"></param>
        /// <param name="whiteSpaces"></param>
        /// <returns></returns>
        private static string TrimWhiteSpaceStart(string value, out string whiteSpaces)
        {
            whiteSpaces = string.Empty;
            if (string.IsNullOrEmpty(value))
            {
                return value;
            }

            if (!char.IsWhiteSpace(value[0]))
            {
                return value;
            }

            for(int i = 0; i < value.Length; i++)
            {
                if (!char.IsWhiteSpace(value, i))
                {
                    whiteSpaces = value.Substring(0, i);
                    return value.Substring(i);
                }
            }

            return string.Empty;
        }

        /// <summary>
        /// 
        /// </summary>
        private class Converter
        {
            private readonly string markdown;
            private int blockquoteLevel;

            /// <summary>
            /// 
            /// </summary>
            /// <param name="markdown"></param>
            public Converter(string markdown)
            {
                this.markdown = markdown;
            }

            /// <summary>
            /// 
            /// </summary>
            /// <returns></returns>
            public string ToHtml()
            {
                if (string.IsNullOrEmpty(markdown))
                {
                    return markdown;
                }

                HtmlWriter writer = new HtmlWriter(markdown.Length + markdown.Length / 2);
                StringReader lineReader = new StringReader(markdown);
                string line;
                while ((line = lineReader.ReadLine()) != null)
                {
                    ProcessLine(writer, line);
                }

                return writer.ToString();
            }

            /// <summary>
            /// 
            /// </summary>
            /// <param name="writer"></param>
            /// <param name="line"></param>
            private void ProcessLine(HtmlWriter writer, string line)
            {
                // 检测是否为代码块
                if (ProcessCoding(writer, line))
                {
                    return;
                }

                string whiteSpaces;
                string withoutStartWhiteSpaceLine = TrimWhiteSpaceStart(line, out whiteSpaces);

                if (string.IsNullOrEmpty(withoutStartWhiteSpaceLine))
                {
                    // 恢复状态
                    writer.NewLine();
                    return;
                }

                char firstChr = withoutStartWhiteSpaceLine[0];

                // 特殊处理嵌套，因为嵌套是可以多层嵌套以及再嵌套其它
                int level = 0;
                while (withoutStartWhiteSpaceLine.StartsWith(">"))
                {
                    level++;
                    withoutStartWhiteSpaceLine = withoutStartWhiteSpaceLine.Substring(1);
                    if (level > blockquoteLevel)
                    {
                        writer.WriteHtml("<blockquote>", true);
                    }
                }

                // 关闭嵌套
                if (level < blockquoteLevel)
                {
                    for (int i = level; i <= blockquoteLevel; i++)
                    {
                        writer.WriteHtml("</blockquote>", true);
                    }
                    blockquoteLevel = level;
                }

                switch (firstChr)
                {
                    // 标题
                    case '#':
                        ProcessHn(writer, withoutStartWhiteSpaceLine);
                        break;

                    // 列表
                    case '-':
                        ProcessUl(writer, withoutStartWhiteSpaceLine, whiteSpaces);
                        break;

                    case '1':
                    case '2':
                    case '3':
                    case '4':
                    case '5':
                    case '6':
                    case '7':
                    case '8':
                    case '9':
                    case '0':
                        ProcessOl(writer, withoutStartWhiteSpaceLine, whiteSpaces);
                        break;

                    default:
                        ProcessP(writer, withoutStartWhiteSpaceLine, whiteSpaces);
                        break;
                }
            }

            #region 处理代码块

            /// <summary>
            /// 处理代码块
            /// </summary>
            /// <param name="writer"></param>
            /// <param name="line"></param>
            /// <returns>已处理完（不需要其它处理）</returns>
            private static bool ProcessCoding(HtmlWriter writer, string line)
            {
                string whiteSpaces;
                string trimedLine = TrimWhiteSpaceStart(line, out whiteSpaces).TrimEnd();

                if (trimedLine.StartsWith("```"))
                {
                    if (writer.IsCoding)
                    {
                        // 多行代码结束
                        writer.RenderEndCoding(@"</code></pre></div>
</div>");
                    }
                    else if (trimedLine.Length > 3 && trimedLine.EndsWith("```"))
                    {
                        // 单行代码
                        writer.RenderStartCoding(@"
<div>
	<div class=""code-container"">
		<pre><code class=""language-any"">");
                        writer.WriteHtml(trimedLine.Substring(0, trimedLine.Length - 3).Substring(3), false);
                        writer.RenderEndCoding(@"</code></pre></div>
</div>");
                    }
                    else
                    {
                        // 多行代码开始
                        const string codeBlockPattern = @"```(\w*)";
                        const string codeBlockReplacement = @"
<div>
	<div class=""code-header"">
		<span class=""code-language"">$1</span>
	</div>
	<div class=""code-container"">
		<pre><code class=""language-$1"">";
                        string codingStart = Regex.Replace(trimedLine, codeBlockPattern, codeBlockReplacement, RegexOptions.Singleline);
                        writer.RenderStartCoding(codingStart);
                    }
                    return true;
                }
                else if (writer.IsCoding)
                {
                    // 代码不做任何处理
                    writer.WriteHtml(line, true);
                    return true;
                }

                return false;
            }

            #endregion

            /// <summary>
            /// 
            /// </summary>
            /// <param name="line"></param>
            /// <returns></returns>
            private static string ProcessInlineElement(string line)
            {
                // 处理图片
                const string imgPattern = @"!\[(.*?)\]\((\S+)(?:\s+""(.*?)""|\s+'(.*?)')?\)";
                const string imgReplacement = "<img src=\"$2\" alt=\"$1\" title=\"$3$4\">";
                line = Regex.Replace(line, imgPattern, imgReplacement);

                // 处理链接
                const string linkPattern = @"\[(.*?)\]\((\S+)(?:\s+[""'](.*?)[""'])?\)";
                const string linkReplacement = "<a href=\"$2\" title=\"$3\" target=\"_blank\">$1</a>";
                line = Regex.Replace(line, linkPattern, linkReplacement);

                // 替换加粗文本
                line = Regex.Replace(line, @"(\*\*|__)(.*?)\1", "<strong>$2</strong>");
                // 替换斜体文本
                line = Regex.Replace(line, @"(\*|_)(.*?)\1", "<em>$2</em>");
                // 替换删除线文本
                line = Regex.Replace(line, @"~~(.*?)~~", "<del>$1</del>");

                // 处理行内代码
                const string inlineCodePattern = @"`([^`]+)`";
                const string inlineCodeReplacement = @"<code>$1</code>";
                line = Regex.Replace(line, inlineCodePattern, inlineCodeReplacement);

                return line;
            }

            /// <summary>
            /// 
            /// </summary>
            /// <param name="writer"></param>
            /// <param name="line"></param>
            private void ProcessHn(HtmlWriter writer, string line)
            {
                line = Regex.Replace(line, @"^(#{1,6})\s+(.*?)(?:\r?\n|$)", delegate (Match match)
                {
                    int level = match.Groups[1].Value.Length;
                    string content = match.Groups[2].Value;
                    return string.Format("<h{0}>{1}</h{0}>", level, ProcessInlineElement(content));
                }, RegexOptions.Multiline);
                writer.CloseAllTag();
                writer.WriteHtml(line, true);
            }

            /// <summary>
            /// 
            /// </summary>
            /// <param name="writer"></param>
            /// <param name="line"></param>
            /// <param name="whiteSpaces"></param>
            private void ProcessUl(HtmlWriter writer, string line, string whiteSpaces)
            {
                if (!line.StartsWith("- "))
                {
                    ProcessP(writer, line, whiteSpaces);
                    return;
                }

                BeforeProcessUlOrOl(writer, line, whiteSpaces, "ul");

                ProcessLi(writer, line.Substring(2).TrimStart(), whiteSpaces);
            }

            /// <summary>
            /// 
            /// </summary>
            /// <param name="writer"></param>
            /// <param name="line"></param>
            /// <param name="whiteSpaces"></param>
            private void ProcessOl(HtmlWriter writer, string line, string whiteSpaces)
            {
                if (!Regex.IsMatch(line, @"\d+\. "))
                {
                    ProcessP(writer, line, whiteSpaces);
                    return;
                }

                BeforeProcessUlOrOl(writer, line, whiteSpaces, "ol");

                int spaceIndex = line.IndexOf(' ');
                ProcessLi(writer, line.Substring(spaceIndex).TrimStart(), whiteSpaces);
            }

            /// <summary>
            /// 
            /// </summary>
            /// <param name="builder"></param>
            /// <param name="line"></param>
            /// <param name="whiteSpaces"></param>
            /// <param name="tagName"></param>
            private void BeforeProcessUlOrOl(HtmlWriter writer, string line, string whiteSpaces, string tagName)
            {
                // 处理前一个标签
                string lastTagName = writer.PeekTagName();
                int lastWhiteSpacesLength = writer.PeekWhiteSpaces().Length;
                switch (lastTagName)
                {
                    case "p":
                        // 关闭段落
                        writer.RenderEndTag();
                        break;

                    case "li":
                        if (lastWhiteSpacesLength == whiteSpaces.Length)
                        {
                            // 关闭同级的上一个标签
                            writer.RenderEndTag();
                        }
                        break;
                }

                if (whiteSpaces.Length > lastWhiteSpacesLength)
                {
                    // 下级列表开始
                    writer.RenderStartTag(tagName, whiteSpaces, true);
                }
                else if (whiteSpaces.Length < lastWhiteSpacesLength)
                {
                    // 退级
                    while (whiteSpaces.Length < lastWhiteSpacesLength)
                    {
                        writer.RenderEndTag();
                        lastWhiteSpacesLength = writer.PeekWhiteSpaces().Length;
                    }
                }
                else
                {
                    if (writer.PeekTagName() == null)
                    {
                        writer.RenderStartTag(tagName, whiteSpaces, true);
                    }
                }
            }

            /// <summary>
            /// 
            /// </summary>
            /// <param name="writer"></param>
            /// <param name="line"></param>
            /// <param name="whiteSpaces"></param>
            private void ProcessLi(HtmlWriter writer, string line, string whiteSpaces)
            {
                writer.RenderStartTag("li", whiteSpaces, true);
                writer.WriteHtml(ProcessInlineElement(HttpUtility.HtmlEncode(line)), false);
            }

            /// <summary>
            /// 
            /// </summary>
            /// <param name="writer"></param>
            /// <param name="line"></param>
            /// <param name="whiteSpaces"></param>
            private void ProcessP(HtmlWriter writer, string line, string whiteSpaces)
            {
                bool beforeNewLine = false;
                if (writer.IsNewLine)
                {
                    if (string.IsNullOrEmpty(whiteSpaces))
                    {
                        writer.TryRenderEndTag();
                        beforeNewLine = true;
                    }
                }

                if (writer.PeekTagName() != "p")
                {
                    writer.RenderStartTag("p", null, beforeNewLine);
                }
                else
                {
                    writer.RenderBrTag();
                }

                writer.WriteHtml(ProcessInlineElement(line), false);
                writer.RenderEndTag();
            }
        }

        #region Html写入器

        /// <summary>
        /// 
        /// </summary>
        private class HtmlWriter
        {
            readonly StringBuilder builder;
            readonly Stack<string> tagNameStack = new Stack<string>();
            readonly Stack<string> whiteSpaceStack = new Stack<string>();
            bool isCoding;
            bool isNewLine;

            public HtmlWriter(int capacity)
            {
                builder = new StringBuilder(capacity);
            }

            /// <summary>
            /// 
            /// </summary>
            /// <param name="tagName"></param>
            /// <param name="whiteSpaces"></param>
            /// <param name="beforeNewLine"></param>
            public void RenderStartTag(string tagName, string whiteSpaces, bool beforeNewLine)
            {
                isNewLine = false;

                if (beforeNewLine)
                {
                    builder.AppendLine()
                        .Append(new String('\t', tagNameStack.Count));
                }
                builder.AppendFormat("<{0}>", tagName);
                tagNameStack.Push(tagName);
                whiteSpaceStack.Push(whiteSpaces);
            }

            /// <summary>
            /// 
            /// </summary>
            public void RenderEndTag()
            {
                isNewLine = false;

                string tagName = tagNameStack.Pop();
                whiteSpaceStack.Pop();
                builder.AppendFormat("</{0}>", tagName);
            }

            /// <summary>
            /// 
            /// </summary>
            public void TryRenderEndTag()
            {
                if (tagNameStack.Count > 0)
                {
                    RenderEndTag();
                }
            }

            /// <summary>
            /// 
            /// </summary>
            public void RenderBrTag()
            {
                isNewLine = false;

                builder.Append("<br>");
            }

            /// <summary>
            /// 
            /// </summary>
            public void CloseAllTag()
            {
                while(tagNameStack.Count > 0)
                {
                    RenderEndTag();
                }
            }

            /// <summary>
            /// 
            /// </summary>
            /// <param name="html"></param>
            /// <param name="afterNewLine"></param>
            public void WriteHtml(string html, bool afterNewLine)
            {
                isNewLine = false;

                builder.Append(html);
                if (afterNewLine)
                {
                    builder.AppendLine();
                }
            }

            /// <summary>
            /// 当前处于空行状态
            /// </summary>
            public bool IsNewLine
            {
                get { return isNewLine; }
            }

            /// <summary>
            /// 空行 - 封口
            /// </summary>
            public void NewLine()
            {
                if (isNewLine)
                {
                    // 连续两个空行，关闭全部标签
                    do
                    {
                        if (PeekTagName() != null)
                        {
                            RenderEndTag();
                            continue;
                        }
                        break;
                    } while (true);
                    isNewLine = false;
                }
                else
                {
                    string lastTagName = PeekTagName();
                    switch (lastTagName)
                    {
                        case "p":
                            // 关闭段落
                            RenderEndTag();
                            break;

                        case "li":
                            // 关闭至第一层
                            RenderEndTag();
                            do
                            {
                                lastTagName = PeekTagName();
                                if (tagNameStack.Count > 1 && (lastTagName == "ul" || lastTagName == "ol" || lastTagName == "li"))
                                {
                                    RenderEndTag();
                                    continue;
                                }

                                break;
                            } while (true);
                            break;

                        case "blockquote":
                            RenderEndTag();
                            do
                            {
                                lastTagName = PeekTagName();
                                if (lastTagName == "blockquote")
                                {
                                    RenderEndTag();
                                    continue;
                                }

                                break;
                            } while (true);
                            break;
                    }

                    isNewLine = true;
                }
            }

            #region 代码

            /// <summary>
            /// 
            /// </summary>
            public bool IsCoding
            {
                get { return isCoding; }
            }

            /// <summary>
            /// 
            /// </summary>
            /// <param name="html"></param>
            public void RenderStartCoding(string html)
            {
                isNewLine = false;

                builder.Append(html);
                isCoding = true;
            }

            /// <summary>
            /// 
            /// </summary>
            /// <param name="html"></param>
            public void RenderEndCoding(string html)
            {
                isNewLine = false;

                builder.AppendLine(html);
                isCoding = false;
            }

            #endregion

            /// <summary>
            /// 
            /// </summary>
            /// <returns></returns>
            public string PeekTagName()
            {
                if (tagNameStack.Count > 0)
                {
                    return tagNameStack.Peek();
                }
                return null;
            }

            /// <summary>
            /// 
            /// </summary>
            /// <returns></returns>
            public string PeekWhiteSpaces()
            {
                if (whiteSpaceStack.Count > 0)
                {
                    return whiteSpaceStack.Peek();
                }
                return string.Empty;
            }

            /// <summary>
            /// 
            /// </summary>
            /// <returns></returns>
            public override string ToString()
            {
                return builder.ToString();
            }
        }

        #endregion
    }
}
